package cn.sciento.boot.api.customize.compile;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;

public class DynamicJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
  private static final String[] superLocationNames = new String[] { StandardLocation.PLATFORM_CLASS_PATH.name(), "SYSTEM_MODULES" };
  
  private final PackageInternalsFinder finder;
  
  private final DynamicClassLoader classLoader;
  
  private final List<MemoryByteCode> byteCodes = new ArrayList<>();
  
  public DynamicJavaFileManager(JavaFileManager fileManager, DynamicClassLoader classLoader) {
    super(fileManager);
    this.classLoader = classLoader;
    this.finder = new PackageInternalsFinder(classLoader);
  }
  
  public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
    for (MemoryByteCode byteCode : this.byteCodes) {
      if (byteCode.getClassName().equals(className))
        return byteCode; 
    } 
    MemoryByteCode innerClass = new MemoryByteCode(className);
    this.byteCodes.add(innerClass);
    this.classLoader.registerCompiledSource(innerClass);
    return innerClass;
  }
  
  public ClassLoader getClassLoader(Location location) {
    return this.classLoader;
  }
  
  public String inferBinaryName(Location location, JavaFileObject file) {
    if (file instanceof CustomJavaFileObject)
      return ((CustomJavaFileObject)file).binaryName(); 
    return super.inferBinaryName(location, file);
  }
  
  public Iterable<JavaFileObject> list(Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException {
    if (location instanceof StandardLocation) {
      String locationName = ((StandardLocation)location).name();
      for (String name : superLocationNames) {
        if (name.equals(locationName))
          return super.list(location, packageName, kinds, recurse); 
      } 
    } 
    if (location == StandardLocation.CLASS_PATH && kinds.contains(JavaFileObject.Kind.CLASS))
      return new IterableJoin<>(super.list(location, packageName, kinds, recurse), this.finder
          .find(packageName)); 
    return super.list(location, packageName, kinds, recurse);
  }
  
  static class IterableJoin<T> implements Iterable<T> {
    private final Iterable<T> first;
    
    private final Iterable<T> next;
    
    public IterableJoin(Iterable<T> first, Iterable<T> next) {
      this.first = first;
      this.next = next;
    }
    
    public Iterator<T> iterator() {
      return new IteratorJoin<>(this.first.iterator(), this.next.iterator());
    }
  }
  
  static class IteratorJoin<T> implements Iterator<T> {
    private final Iterator<T> first;
    
    private final Iterator<T> next;
    
    public IteratorJoin(Iterator<T> first, Iterator<T> next) {
      this.first = first;
      this.next = next;
    }
    
    public boolean hasNext() {
      return (this.first.hasNext() || this.next.hasNext());
    }
    
    public T next() {
      if (this.first.hasNext())
        return this.first.next(); 
      return this.next.next();
    }
    
    public void remove() {
      throw new UnsupportedOperationException("remove");
    }
  }
}
